package com.matthewmadson.util.search.yahooboss;

import java.io.IOException;
import java.util.Collection;
import java.util.List;

import org.json.JSONException;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.gdata.util.common.base.CharEscaper;
import com.google.gdata.util.common.base.CharEscaperBuilder;

public class YBWebQuery
{
	private final String appID;
	private final List<String> positiveSearchTerms;
	private final List<String> negativeSearchTerms;
	private final List<String> positiveDomains;
	private final List<String> negativeDomains;
	private final List<String> positiveDocumentTypes;
	private final List<String> negativeDocumentTypes;
	
	private int start;
	private int count;
	private String language;
	private String region;
	private boolean pornFilterEnabled;
	private boolean hateFilterEnabled;
	
	private static final String URI = "http://boss.yahooapis.com/ysearch/web/v1/";
	private static final CharEscaper yahooBossEscaper = new CharEscaperBuilder()
		.addEscape('/', "%2F")
		.addEscape('?', "%3F")
		.addEscape('&', "%3F")
		.addEscape(';', "%3F")
		.addEscape(':', "%3F")
		.addEscape('@', "%3F")
		.addEscape(',', "%3F")
		.addEscape('$', "%3F")
		.addEscape('=', "%3F")
		.addEscape(' ', "%3F")
		.addEscape('%', "%3F")
		.addEscape('"', "%3F")
		.addEscape('+', "%3F")
		.addEscape('#', "%3F")
		.addEscape('*', "%3F")
		.addEscape('<', "%3F")
		.addEscape('>', "%3F")
		.addEscape('{', "%3F")
		.addEscape('}', "%3F")
		.addEscape('|', "%3F")
		.addEscape('[', "%3F")
		.addEscape(']', "%3F")
		.addEscape('^', "%3F")
		.addEscape('\\', "%3F")
		.addEscape('`', "%3F").toEscaper();
	
	public YBWebQuery(final String yahooApplicationID)
	{
		this.appID = yahooApplicationID;
		this.positiveSearchTerms = Lists.newArrayList();
		this.negativeSearchTerms = Lists.newArrayList();
		this.positiveDomains = Lists.newArrayList();
		this.negativeDomains = Lists.newArrayList();
		this.positiveDocumentTypes = Lists.newArrayList();
		this.negativeDocumentTypes = Lists.newArrayList();
		this.start = 0;
		this.count = 50;
		this.language = "en";
		this.region = "us";
		this.pornFilterEnabled = false;
		this.hateFilterEnabled = false;
	}

	public void addSearchTerm(final String searchTerm)
	{
		this.positiveSearchTerms.add(searchTerm);
	}

	public void addSearchTerms(final Collection<? extends String> searchTerms)
	{
		this.positiveSearchTerms.addAll(searchTerms);
	}

	public void addExcludedSearchTerm(final String searchTerm)
	{
		this.negativeSearchTerms.add(searchTerm);
	}

	public void addExcludedSearchTerms(final Collection<? extends String> searchTerms)
	{
		this.negativeSearchTerms.addAll(searchTerms);
	}

	public void addDomain(final String domain)
	{
		this.positiveDomains.add(domain);
	}

	public void addExcludedDomain(final String domain)
	{
		this.negativeDomains.add(domain);
	}

	public void setStart(final int start)
	{
		this.start = start;
	}

	public void setResultsPerPage(final int count)
	{
		this.count = count;
	}

	public void setLocale(final String language, final String region)
	{
		this.language = language;
		this.region = region;
	}

	public void enablePornFilter(final boolean enabled)
	{
		this.pornFilterEnabled = enabled;
	}

	public void enableHateFilter(final boolean enabled)
	{
		this.hateFilterEnabled = enabled;
	}

	public void addDocumentType(final String type)
	{
		this.positiveDocumentTypes.add(type);
	}

	public void addExcludedDocumentType(final String type)
	{
		this.negativeDocumentTypes.add(type);
	}

	public void addDomains(final Collection<? extends String> domains)
	{
		this.positiveDomains.addAll(domains);
	}

	public void addExcludedDomains(final Collection<? extends String> domains)
	{
		this.negativeDomains.addAll(domains);
	}

	public void addDocumentTypes(final Collection<? extends String> types)
	{
		this.positiveDocumentTypes.addAll(types);
	}

	public void addExcludedDocumentTypes(final Collection<? extends String> types)
	{
		this.negativeDocumentTypes.addAll(types);
	}
		
	public YBWebQueryResultSet getResultSet() 
	throws IOException, JSONException
	{
		return new YBWebQueryResultSet(this.buildQuery());
	}

	public String buildQuery()
	{
		String temp = "";
		
		StringBuilder query = new StringBuilder();
		query.append(URI);
		query.append("\"");
		query.append(yahooBossEscaper.escape(Joiner.on(" ").join(this.positiveSearchTerms)));
		query.append("\"");

		for(String searchTerm : this.negativeSearchTerms)
			query.append("-\""+yahooBossEscaper.escape(searchTerm)+"\"");

		for(String domain : this.negativeDomains)
			query.append("-site:"+domain);
		
		temp = query.toString().trim();
		query.setLength(0);
		query.append(temp);
		
		query.append("?");
		query.append("appid="+this.appID);
		query.append("&start="+this.start);
		query.append("&count="+this.count);
		query.append("&lang="+this.language);
		query.append("&region="+this.region);
		query.append("&format=json");
		query.append("&sites="+Joiner.on(",").join(this.positiveDomains));
		
		if(this.pornFilterEnabled && this.hateFilterEnabled)
			query.append("&filter=-hate,-porn");
		else if(this.pornFilterEnabled)
			query.append("&filter=-porn");
		else if(this.hateFilterEnabled)
			query.append("&filter=-hate");
		
		query.append("&type="+Joiner.on(",").join(Iterables.concat(this.positiveDocumentTypes, Iterables.transform(this.negativeDocumentTypes, new Function<String,String>()
			{
				public String apply(String element)
				{
					return "-"+element;
				}
			}))));
		
		return query.toString().trim();
	}
}
